﻿/*
    Project: IV:MP GUI Editor (http://code.google.com/p/ivmp-gui-editor)
    Code license: GNU Lesser GPL (http://www.gnu.org/copyleft/lesser.html) 
 */

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace IvmpEditor
{
    public class WpfMoveControlAgent
    {
        // Static stuff:
        public static Point GetCursorPos(Control relativeCtrl)
        {
            var cPos = Mouse.GetPosition(relativeCtrl.Parent as IInputElement);
            return new Point(cPos.X, cPos.Y);
        }
        public static Point GetControlPos(Control ctrl)
        {
            return new Point(
                ctrl.Margin.Left,
                ctrl.Margin.Top
                );
        }
        public static Point GetPanelPos(Panel p)
        {
            return new Point(p.Margin.Left - p.Margin.Right, p.Margin.Top - p.Margin.Bottom);
        }
        public static Point GetControlSize(Control ctrl)
        {
            return new Point(
                ctrl.ActualWidth,
                ctrl.ActualHeight
                );
        }
        public static Point GetPanelSize(Panel p)
        {
            return new Point(
                p.ActualWidth,
                p.ActualHeight
                );
        }

        // FIXME: remove this shit:
        public static void SetControlPos(FrameworkElement ctrl, Point newPos)
        {
            if (double.IsNaN(newPos.X))
                newPos.X = 0;
            if (double.IsNaN(newPos.Y))
                newPos.Y = 0;
            ctrl.Margin = new Thickness(newPos.X, newPos.Y, ctrl.Margin.Right, ctrl.Margin.Bottom);
        }
        public static void SetControlSize(FrameworkElement ctrl, Point newSz)
        {
            ctrl.Width = newSz.X;
            ctrl.Height = newSz.Y;
        }

        public static bool IsDoubleInRange(double x, double min, double max) { return x >= min && x <= max; }
        public static bool IsPointInBox2D(Point pt, Point controlPos, Point controlSize)
        {
            return IsDoubleInRange(pt.Y, controlPos.Y, controlPos.Y + controlSize.Y) &&
                IsDoubleInRange(pt.X, controlPos.X, controlPos.X + controlSize.X);
        }
        public static bool IsPointOverPanel(Point pt, Panel p)
        {
            return IsPointInBox2D(pt, GetPanelPos(p), GetPanelSize(p));
        }
        public static bool IsPointOverControl(Point pt, Control c)
        {
            return IsPointInBox2D(pt, GetControlPos(c), GetControlSize(c));
        }

        public WpfMoveControlAgent(Control forW) { Ctrl = forW; }
        public void InstallTo(FrameworkElement[] controls)
        {
            if (IsInstalled)
                throw new InvalidOperationException("First you need to Uninstall hooks.");

            foreach (FrameworkElement c in controls)
            {
                c.PreviewMouseDown += c_PreviewMouseDown;
                c.MouseLeave += c_MouseLeave;
                c.PreviewMouseMove += c_PreviewMouseMove;
                c.PreviewMouseUp += c_PreviewMouseUp;
                c.PreviewLostKeyboardFocus += c_PreviewLostKeyboardFocus;
                c.LostFocus += c_LostFocus;
                c.LostMouseCapture += c_LostMouseCapture;
            }
            Installed = controls;
            enabled = true;
        }
        public void Uninstall()
        {
            if (IsInstalled == false)
                return;

            foreach (FrameworkElement c in Installed)
            {
                c.MouseLeave += c_MouseLeave;
                c.PreviewMouseDown -= c_PreviewMouseDown;
                c.PreviewMouseMove -= c_PreviewMouseMove;
                c.PreviewMouseUp -= c_PreviewMouseUp;
                c.PreviewLostKeyboardFocus -= c_PreviewLostKeyboardFocus;
                c.LostFocus -= c_LostFocus;
                c.LostMouseCapture -= c_LostMouseCapture;
            }
            Installed = null;
        }

        public bool IsMovingNow
        {
            get { return moving; }
            set
            {
                if (value)
                    throw new ArgumentException("You can use IsMovingNow._set property only to stop movingNow.");
                else if (IsMovingNow)
                    EndMoving();
            }
        }
        public bool IsEnabled
        {
            get { return enabled; }
            set
            {
                enabled = value;
                if (IsMovingNow && !IsEnabled)
                    EndMoving();
            }
        }
        public bool IsInstalled { get { return Installed != null; } }
        public void EndMoving()
        {
            if (IsMovingNow == false)
                return;

            Ctrl.Cursor = WasCursor;
            moving = false;
            onAgentMovedControl(Ctrl, GetControlPos(Ctrl));
        }
        public event AgentMovedControlEventHandler AgentMovedControl;
        
        // Internal (MoveControlAgent)
        private Control Ctrl;
        private FrameworkElement[] Installed;
        private bool moving, enabled;
        private Point lastCursorPos;
        private Cursor WasCursor;

        void c_MouseLeave(object sender, MouseEventArgs e)
        {
            if (!moving || Ctrl is Label)
                return;

            EndMoving();
        }
        void c_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (moving || sender != Ctrl || e.ChangedButton != MouseButton.Left)
                return;

            WasCursor = Ctrl.Cursor;
            Ctrl.Cursor = Cursors.SizeAll;
            lastCursorPos = GetCursorPos(Ctrl);
            moving = true;
        }
        void c_PreviewMouseUp(object sender, MouseButtonEventArgs e)
        {
            if (!moving || sender != Ctrl || e.ChangedButton != MouseButton.Left)
                return;

            EndMoving();
        }
        void c_PreviewMouseMove(object sender, MouseEventArgs e)
        {
            if (!moving || e.LeftButton != MouseButtonState.Pressed)
                return;

            // Get current state
            var editorPanel = Ctrl.Parent as Panel;
            var cursorPos = GetCursorPos(Ctrl);
            var ctrlPos = GetControlPos(Ctrl);
            var ctrlSize = GetControlSize(Ctrl);
            var editorPanelSize = GetPanelSize(editorPanel);
            bool cursorOverPanel = IsPointOverPanel(cursorPos, editorPanel);
            bool cursorOverCtrl = IsPointOverControl(cursorPos, Ctrl);

            // Check:
            if (!cursorOverPanel)
                return; // we dont care moves outside editor
            if (!cursorOverCtrl)
            {
                // If cursor is outside control, we move control's center to cursor pos.
                ctrlPos = new Point(
                    cursorPos.X - ctrlSize.X / 2,
                    cursorPos.Y - ctrlSize.Y / 2);
            }
            else
            {
                // If cursor is inside editor&control we move control by calculating offset
                if (cursorPos.X < lastCursorPos.X)
                    ctrlPos.X -= lastCursorPos.X - cursorPos.X;
                else if (cursorPos.X > lastCursorPos.X)
                    ctrlPos.X += cursorPos.X - lastCursorPos.X;
                if (cursorPos.Y < lastCursorPos.Y)
                    ctrlPos.Y -= lastCursorPos.Y - cursorPos.Y;
                else if (cursorPos.Y > lastCursorPos.Y)
                    ctrlPos.Y += cursorPos.Y - lastCursorPos.Y;
            }

            // Check & fix result position (control should be fully inside editor panel)
            if (ctrlPos.X < 0) ctrlPos.X = 0;
            if (ctrlPos.Y < 0) ctrlPos.Y = 0;
            var ctrlEndPos = new Point(ctrlPos.X + ctrlSize.X, ctrlPos.Y + ctrlSize.Y);
            if (!IsPointInBox2D(ctrlEndPos, new Point(-Ctrl.Margin.Left, -Ctrl.Margin.Top), editorPanelSize))
            {
                editorPanelSize.X -= Canvas.GetLeft(Ctrl) + Canvas.GetRight(Ctrl);
                editorPanelSize.Y -= Canvas.GetTop(Ctrl) + Canvas.GetBottom(Ctrl);
                if (ctrlEndPos.X > editorPanelSize.X)
                    ctrlPos.X -= ctrlEndPos.X - editorPanelSize.X;
                if (ctrlEndPos.Y > editorPanelSize.Y)
                    ctrlPos.Y -= ctrlEndPos.Y - editorPanelSize.Y;
            }

            // Update current state and pos:
            SetControlPos(Ctrl, ctrlPos);
            lastCursorPos = cursorPos;
        }
        void c_PreviewLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) { EndMoving(); }
        void c_LostFocus(object sender, RoutedEventArgs e) { EndMoving(); }
        void c_LostMouseCapture(object sender, MouseEventArgs e) { EndMoving(); }

        public delegate void AgentMovedControlEventHandler(Control ctrl, Point newPos);
        private void onAgentMovedControl(Control ctrl, Point newPos)
        {
            if (this.AgentMovedControl != null)
                this.AgentMovedControl(ctrl, newPos);
        }
    }
}
